#include <cassert>
#include <iostream>
#include <map>
#include <deque>
#include <algorithm>
#include <cmath>
#include <iomanip>

#include <vector>
#include <fstream>
#include <sstream>

#include "data_server.h"


using namespace std;

const int total_files_xyz = 2;
const int CONSUMER = -1;


void die(const string &message) {
  cout << "die ocurred: " << message << endl;
  cerr <<"die occured: " <<  message << endl;
  exit(-1);
}

class DeterministicPseudoRandomServer: public Server {
	public:
		enum ColType { INT, DOUBLE, BOOL };

    DeterministicPseudoRandomServer(int n): _n(n), _fileNumber(CONSUMER){}
		DeterministicPseudoRandomServer(int n, int fileNumber, int qid): _n(n), 
      _fileNumber(fileNumber) {
        string s;
        stringstream myStream(stringstream::in | stringstream::out);
        myStream << "data/q" << qid << "-" << (_fileNumber + 1) << ".data";
        
        s = myStream.str();
        fstream fileStream(s.c_str(), ios_base::in);
        if (fileStream.fail()) {
          die("cannot open file: " + s);
        }
        readFile(fileStream);
      }


    void readFile(fstream &fileStream) {
      int columns;
      fileStream >> columns;
      tokens = vector< vector<string> >(columns);
      indexes = vector<int>(columns);

      for(int i = 0; i < indexes.size(); ++i) {
        indexes[i] = 0;
      }


      while(fileStream.good()) {
        for(int i = 0; i < columns; ++i) {
          string s;
          fileStream >> s;
          if (!fileStream.good())
            break;
          tokens[i].push_back(s);
        }
      }


      /*for(int i = 0; i < columns; ++i) {
        cerr << "kolumna " << i << " ma " << tokens[i].size() << " wierszy " << endl;
        cerr << "ostatni element " << tokens[i][tokens[i].size() - 1] << endl;
      }*/
    }

		// We need to print everything here, because we don't know amount
		// of output columns before consuming everything, and we don't know
		// when we finally consumed everything.
		~DeterministicPseudoRandomServer() {
			int amount = 0;
			int columns = 0;

#ifndef NO_OUTPUT
			// check that amount of data in output columns is equal
			for (map<int, deque<int32_t> >::const_iterator i = _intQueue.begin(); i != _intQueue.end(); ++i) {
				if (amount == 0) {
					amount = i->second.size();
				} else {
					assert(amount == (int) i->second.size());
				}

				columns = max(columns, i->first + 1);
			}

			for (map<int, deque<double> >::const_iterator i = _doubleQueue.begin(); i != _doubleQueue.end(); ++i) {
				if (amount == 0) {
					amount = i->second.size();
				} else {
					assert(amount == (int) i->second.size());
				}

				columns = max(columns, i->first + 1);
			}

			for (map<int, deque<bool> >::const_iterator i = _boolQueue.begin(); i != _boolQueue.end(); ++i) {
				if (amount == 0) {
					amount = i->second.size();
				} else {
					assert(amount == (int) i->second.size());
				}

				columns = max(columns, i->first + 1);
			}

			// print everything in rows
			for (int i = 0; i < amount; ++i) {
				for (int col = 0; col < columns; ++col) {
					if (_intQueue.find(col) != _intQueue.end()) {
						cerr << setw(15) << _intQueue[col].front();
						_intQueue[col].pop_front();
					} else if (_doubleQueue.find(col) != _doubleQueue.end()) {
						cerr << setw(15) << _doubleQueue[col].front();
						_doubleQueue[col].pop_front();
					} else {
						assert(_boolQueue.find(col) != _boolQueue.end());
						cerr << setw(7) << boolalpha << _boolQueue[col].front();
						_boolQueue[col].pop_front();
					}
				}

				cerr << "\n";
			}
#endif
		}


    int getColumnSize(int columnIndex) {
      return tokens[columnIndex].size();
    }

    template<class T>
    int getNumbers(int columnIndex, int number, T *destination) {
      int numberCounter = 0;
      while(numberCounter < number && indexes[columnIndex] < getColumnSize(columnIndex)) {
        stringstream ss(tokens[columnIndex][indexes[columnIndex]]);
        T elem;
        ss >> elem;
        destination[numberCounter] = elem;
        ++numberCounter;
        ++indexes[columnIndex];
      }
      return numberCounter;
    }
    
		virtual int GetDoubles(int column_index, int number, double* destination) {
      return getNumbers<double>(column_index, number, destination);
		}

		virtual int GetInts(int column_index, int number, int32_t* destination) {
      return getNumbers<int32_t>(column_index, number, destination);
		}

		virtual int GetByteBools(int column_index, int number, bool* destination) {
      return getNumbers<bool>(column_index, number, destination);
		}

		
    virtual int GetBitBools(int column_index, int number, char* destination) {
			assert(false); // NOT IMPLEMENTED
			return 0;
		}

		virtual void ConsumeDoubles(int column_index, int number, const double* destination) {
			checkOutputType(column_index, DOUBLE);

#ifndef NO_OUTPUT
			for (int i = 0; i < number; ++i) {
				_doubleQueue[column_index].push_back(destination[i]);
			}
#endif
		}

		virtual void ConsumeInts(int column_index, int number, const int32_t* destination) {
			checkOutputType(column_index, INT);

#ifndef NO_OUTPUT
			for (int i = 0; i < number; ++i) {
				_intQueue[column_index].push_back(destination[i]);
			}
#endif
		}

		virtual void ConsumeByteBools(int column_index, int number, const bool* destination) {
			checkOutputType(column_index, BOOL);

#ifndef NO_OUTPUT
			for (int i = 0; i < number; ++i) {
				_boolQueue[column_index].push_back(destination[i]);
			}
#endif
		}

		virtual void ConsumeBitBools(int column_index, int number, const char* destination) {
			assert(false); // NOT IMPLEMENTED
		}

	private:
		void checkOutputType(int column_index, ColType type) {
			if (_outputTypes.find(column_index) == _outputTypes.end()) {
				_outputTypes[column_index] = type;
			} else {
				assert(_outputTypes[column_index] == type);
			}
		}


    
    vector< vector<string> > tokens;
    vector<int> indexes;
    int _fileNumber;
		int _n;
		map<int, int> _dataLeft;
		map<int, ColType> _types;
		map<int, ColType> _outputTypes;
		map<int, deque<int32_t> > _intQueue;
		map<int, deque<double> > _doubleQueue;
		map<int, deque<bool> > _boolQueue;
};

Server* CreateDataSinkServer(int query_id, int nn) {
	ios_base::sync_with_stdio(false);
	return new DeterministicPseudoRandomServer(nn);
}

Server *CreateDataSourceServer(int query_id, int fileNumber, int nn) {
  ios_base::sync_with_stdio(false);
  return new DeterministicPseudoRandomServer(nn, fileNumber, query_id);
}

